package com.wwg.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 *类描述:针对添加@DistLock注解的方法进行加锁
 *
 *@Author:武晓波
 *@date:2019年5月8日
 *@Version:2.1
 */
@Aspect
@Component
public class DistLockAspect {
	
	private static final Logger logger = LoggerFactory.getLogger(DistLockAspect.class);	
	
	@Autowired
	private RedisTemplate redisTemplate;

	@Around("execution(* com.wsn.jfpt..*.*(..)) && @annotation(com.wsn.jfpt.common.annotation.DistLock)")
	public Object lock(ProceedingJoinPoint pjp) throws Throwable {
		
		DistLock lockInfo = getLockInfo(pjp);
		if (lockInfo == null) {
			throw new IllegalArgumentException("配置参数错误");
		}

		String synKey = getSynKey(pjp, lockInfo.synKey());
		logger.debug(synKey);
		if (synKey == null || "".equals(synKey)) {
			throw new IllegalArgumentException("配置参数synKey错误");
		}

		boolean lock = false;
		Object obj = null;
		try {
			// 超时时间
			long maxSleepMills = System.currentTimeMillis() + lockInfo.maxSleepMills();
			long keepMills = System.currentTimeMillis() + lockInfo.keepMills();
			
			while (!lock) {
				lock = setIfAbsent(synKey, keepMills);
				// 得到锁，没有人加过相同的锁
				if (lock) {
					obj = pjp.proceed();
				}
				//是否等待
				else if(!lockInfo.isWait()){
					break;
				}
				// 锁设置了没有超时时间
				else if (lockInfo.keepMills() == 0) {
					// 继续等待获取锁
					if (lockInfo.toWait()) {
						// 如果超过最大等待时间抛出异常
						if (lockInfo.maxSleepMills() > 0 && System.currentTimeMillis() > maxSleepMills) {
							throw new TimeoutException("获取锁资源等待超时");
						}
						TimeUnit.MILLISECONDS.sleep(lockInfo.sleepMills());
					} else {
						break;
					}
				}
				// 已过期，并且getAndSet后旧的时间戳依然是过期的，可以认为获取到了锁
				else if (System.currentTimeMillis() > getLock(synKey) && (System.currentTimeMillis() > getSet(synKey, keepMills))) {
					lock = true;
					obj = pjp.proceed();
				}
				// 没有得到任何锁
				else {
					// 继续等待获取锁
					if (lockInfo.toWait()) {
						// 如果超过最大等待时间抛出异常
						if (lockInfo.maxSleepMills() > 0 && System.currentTimeMillis() > maxSleepMills) {
							throw new TimeoutException("获取锁资源等待超时");
						}
						TimeUnit.MILLISECONDS.sleep(lockInfo.sleepMills());
					}
					// 放弃等待
					else {
						break;
					}
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		} finally {
			// 如果获取到了锁，释放锁
			if (lock) {
				releaseLock(synKey);
			}
		}
		return obj;
	}

	/**  
	 * @param pjp
	 * @param synKey
	 * @return  
	 * @功能描述: 获取包括方法参数上的key redis key的拼写规则为 "RedisSyn_" + synKey + @DistLockKey
	 * @Author:武晓波
	 * @date:2019年5月9日  上午9:08:49
	 * @Version:2.1 
	 */
	private String getSynKey(ProceedingJoinPoint pjp, String synKey) {
		try {
			synKey = "RedisSyn_" + synKey;
			Object[] args = pjp.getArgs();
			if (args != null && args.length > 0) {
				MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
				Annotation[][] paramAnnotationArrays = methodSignature.getMethod().getParameterAnnotations();

				SortedMap<Integer, String> keys = new TreeMap<Integer, String>();

				for (int ix = 0; ix < paramAnnotationArrays.length; ix++) {
					DistLockKey distLockKey = getAnnotation(DistLockKey.class, paramAnnotationArrays[ix]);
					if (distLockKey != null) {
						Object arg = args[ix];
						if (arg != null) {
							keys.put(distLockKey.index(), arg.toString());
						}
					}
				}

				if (keys != null && keys.size() > 0) {
					for (String key : keys.values()) {
						synKey = synKey + key;
					}
				}
			}

			return synKey;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	private static <T extends Annotation> T getAnnotation(final Class<T> annotationClass, final Annotation[] annotations) {
		if (annotations != null && annotations.length > 0) {
			for (final Annotation annotation : annotations) {
				if (annotationClass.equals(annotation.annotationType())) {
					return (T) annotation;
				}
			}
		}

		return null;
	}

	/**  
	 * @param pjp
	 * @return  
	 * @功能描述:获取RedisLock注解信息
	 * @Author:武晓波
	 * @date:2019年5月9日  上午9:08:27
	 * @Version:2.1 
	 */
	private DistLock getLockInfo(ProceedingJoinPoint pjp) {
		try {
			MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
			Method method = methodSignature.getMethod();
			DistLock lockInfo = method.getAnnotation(DistLock.class);
			return lockInfo;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public BoundValueOperations<String, Long> getOperations(String key) {
		return redisTemplate.boundValueOps(key);
	}

	public boolean setIfAbsent(String key, Long value) {
		return getOperations(key).setIfAbsent(value);
	}

	public Long getLock(String key) {
		Long time = getOperations(key).get();
		if (time == null) {
			return 0L;
		}
		return time;
	}

	public Long getSet(String key, Long value) {
		Long time = getOperations(key).getAndSet(value);
		if (time == null) {
			return 0L;
		}
		return time;
	}

	public void releaseLock(String key) {
		redisTemplate.delete(key);
	}
}